/*
 * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <iostream>
#include <fstream>
#include "MxBase/E2eInfer/ImageProcessor/ImageProcessor.h"
#include "MxBase/E2eInfer/VideoDecoder/VideoDecoder.h"
#include "MxBase/E2eInfer/DataType.h"
#include "MxBase/E2eInfer/Size/Size.h"
#include "MxBase/E2eInfer/Image/Image.h"
#include "MxBase/E2eInfer/GlobalInit/GlobalInit.h"
#include "MxBase/E2eInfer/Model/Model.h"
#include "MxBase/E2eInfer/Tensor/Tensor.h"
#include "./inc/gesture_detectv2.h"
#include <chrono>
#include <sys/stat.h>
using namespace std;
using namespace MxBase;
using namespace chrono;
#define FRAME_LENGTH 30
#define KEY_POINTS 14
#define SKELETON_CENTER 2
constexpr int Con50 = 50;
constexpr int Con4000 = 4000;
constexpr int Con128 = 128;
constexpr int Con6 = 6;
constexpr int Con5 = 5;
constexpr int Con2 = 2;
constexpr int Con8 = 8;
constexpr float Con5_0 = 5.0;
constexpr int Con30 = 30;
constexpr int Con1 = 1;
constexpr int Con11 = 11;
int gSuccessNum = -4;
string OpenPoseModelPath = "./model/pose_deploy.om";
string GestureModelPath = "./model/stgcn_fps30_sta_ho_ki4.om";
std::shared_ptr<EngineTransNewT> gMotionDataNew = std::make_shared<EngineTransNewT>();
int gImageNum = 0;

// GetImage
APP_ERROR ReadBinFile(const string& fileName, void*& data, uint32_t& size) {
    struct stat sBuf;
    int fileStatus = stat(fileName.data(), &sBuf);
    if (fileStatus == -1) {
        LogError << "failed to get file" << endl;
        return APP_ERR_COMM_INVALID_PARAM;
    }

    if (S_ISREG(sBuf.st_mode) == 0) {
        LogError << fileName.c_str() << " is not a file, please enter a file" << endl;
        return APP_ERR_COMM_INVALID_PARAM;
    }

    std::ifstream binFile(fileName, std::ifstream::binary);
    if (binFile.is_open() == false) {
        LogError << "open file" << fileName.c_str() << "failed" << endl;
        return APP_ERR_COMM_INVALID_PARAM;
    }

    binFile.seekg(0, binFile.end);
    uint32_t binFileBufferLen = binFile.tellg();
    if (binFileBufferLen == 0) {
        LogError << "binfile is empty, filename is " << fileName.c_str() << endl;
        binFile.close();
        return APP_ERR_COMM_INVALID_PARAM;
    }

    binFile.seekg(0, binFile.beg);
    uint8_t* binFileBufferData = new(std::nothrow) uint8_t[binFileBufferLen];
    if (binFileBufferData == nullptr) {
        LogError << "malloc binFileBufferData failed" << endl;
        binFile.close();
        return APP_ERR_COMM_INVALID_PARAM;
    }

    binFile.read(reinterpret_cast < char * > (binFileBufferData), binFileBufferLen);
    binFile.close();
    data = binFileBufferData;
    size = binFileBufferLen;
    return APP_ERR_OK;
}

// Standardized bone key point sequence
APP_ERROR ProcessOpenPoseData() {
    float totalBottom = 0;
    // The pixel length of human torso in the first five frames is calculated and normalized as the standard
    for (int picNum = FRAME_LENGTH-1; picNum > FRAME_LENGTH-Con6; picNum--) {
        totalBottom +=  float(gMotionDataNew->data[0][1][picNum][Con8] + gMotionDataNew->data[0][1][picNum][Con11]) / Con2 - float(gMotionDataNew->data[0][1][picNum][1]);
    }
    
    totalBottom /= Con5_0;
    for (int picNum = 0; picNum < FRAME_LENGTH; picNum++) {
        for (int key_num = 0; key_num < KEY_POINTS; key_num++) {
            gMotionDataNew->data[0][0][picNum][key_num] /= totalBottom;
            gMotionDataNew->data[0][1][picNum][key_num] /= totalBottom;
        }
    }
    return APP_ERR_OK;
}


int main(int argc, char *argv[]) {
    APP_ERROR ret;
    ret = MxInit();
    if (ret != APP_ERR_OK) {
        LogError << "MxInit failed, ret=" << ret << ".";
    }

    int32_t deviceId_ = 0;
    Model openpose(OpenPoseModelPath, deviceId_);
    Model stgcn(GestureModelPath, deviceId_);
    Image imageData;
    ImageProcessor imageProcessor(deviceId_);
    MxBase::Image decodedImage;
    MxBase::Image resizedImage;
    MxBase::Size resizeConfig(Con128, Con128);
    GestureDetectV2 detect;
    for (gImageNum = 0; gImageNum < Con50; gImageNum++) {
        string imagePath = "./data/" + to_string(gImageNum) + ".jpg";
        void* data;
        uint32_t dataSize;
        ret = ReadBinFile(imagePath, data, dataSize);
        std::shared_ptr<uint8_t> dataPtr((uint8_t*)data);
        if (ret != APP_ERR_OK) {
          LogError << "Getimage failed, ret=" << ret;
          return ret;
        }
        
        ret = imageProcessor.Decode(dataPtr, dataSize, decodedImage, ImageFormat::YUV_SP_420);
        if (ret != APP_ERR_OK) {
            std::cout << "imageProcessor1 Decode failed." << std::endl;
            return 0;
        }
        
        ret = imageProcessor.Resize(decodedImage, resizeConfig, resizedImage, Interpolation::HUAWEI_HIGH_ORDER_FILTER);
        if (ret != APP_ERR_OK) {
            std::cout << "imageProcessor2 Resize failed." << std::endl;
            return 0;
        }

        Tensor tensorImg = resizedImage.ConvertToTensor();
        tensorImg.ToDevice(deviceId_);
        vector<Tensor> openposeInputs = {tensorImg};
        vector<Tensor> openposeOutputs = openpose.Infer(openposeInputs);
        for (size_t i = 0; i<openposeOutputs.size(); i++) {
            openposeOutputs[i].ToHost();
        }
         
        ret = detect.Postprocess(resizedImage, openposeOutputs, gMotionDataNew, gSuccessNum);
        if (ret != APP_ERR_OK) {
            continue;
        }

        if (gSuccessNum % Con5 == 0) {
            ProcessOpenPoseData();
            // The skeleton sequence of human body is sent into the structure model for inference, and the possibility of five actions is obtained
            std::vector<Tensor> gestureOutput;
            gMotionDataNew->bufferSize = SKELETON_CENTER * FRAME_LENGTH * KEY_POINTS * sizeof(float);
            std::vector<uint32_t> shape = { Con1, SKELETON_CENTER, FRAME_LENGTH, KEY_POINTS };
            MxBase::Tensor tensor1((void*) gMotionDataNew->data, shape, MxBase::TensorDType::FLOAT32, deviceId_);
            std::vector<Tensor> stgcnInputs = {tensor1};
            gestureOutput = stgcn.Infer(stgcnInputs);
            size_t x = gestureOutput.size();
            for (size_t i = 0; i<x; i++) {
                gestureOutput[i].ToHost();
            }
            // post processing
            ret = detect.PostGestureProcess(gestureOutput);
            if (ret != APP_ERR_OK) {
                LogError <<"Process model inference output data failed";
                break;
            }
        }
    }

    return 0;
}